/*
 *  Copyright (C) 2010:
 *                 Buzzanca Marco <marco.bzn@gmail.com>
 *         Monaco Davide Giuseppe <black.ralkass@gmail.com>
 *              Petralia Veronica <veronicapetralia@gmail.com>
 *
 * This file is part of DS2011BMP
 *
 * DS2011BMP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * DS2011BMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *
 * NOTE: this file should not be included directly,
 *       Include udpclient.h instead.
 */

/**
 * @file udpclient.cpp
 * @author Marco Buzzanca
 *
 * This file contains UDPClient class declaration and implementation.
 */

#include <boost/asio.hpp>
#include <boost/cstdint.hpp>
#include <string>
#include <sstream>
#include "udpinterface.h"
#include "address.h"

/**
 * UDP Client
 *
 * Basic implementation of an UDP Client. In order to use this class, there
 * should be an existing server ready to accept connections. First, it is
 * required to resolve server endpoint, either in construction step, or later
 * with the resolve() method. After that, the client is ready to submit requests
 * via the send() method. Server response may be collected with the receive()
 * method.
 */
class UDPClient : public UDPSenderReceiver
{
private:
    /**
     * @brief udp::socket shared pointer typedef.
     */
    typedef boost::shared_ptr<boost::asio::ip::udp::socket> SocketPointer;

    /**
     * @brief Provides basic I/O services.
     */
    boost::asio::io_service _io_service;

    /**
     * @brief Communication endpoint.
     */
    boost::asio::ip::udp::endpoint _endpoint;

    /**
     * @brief UDP Socket. Noncopyable, it must be handled with pointers.
     */
    SocketPointer _socket;

    /**
     * @brief Address object containing server IP and port.
     */
    Address _server_address;

public:
    /**
     * @brief Default constructor.
     *
     * Creates an instance of an UDP socket suitable for client communication.
     */
    // NOTE: Calling _io_service() explicitely is useless. (Dave)
    UDPClient() : _io_service()
    {
        using boost::asio::ip::udp;

        _socket = SocketPointer
            (new udp::socket(_io_service, udp::endpoint(udp::v4(), 0)));
    }

    /**
     * @brief Creates an instance of an UDP socket suitable for client
     * communication and sets server address from specified IP and port.
     *
     * @param IP   Provided IP address.
     * @param port Provided port.
     */
    UDPClient(const std::string& IP, boost::uint16_t port)
    : _io_service()
    {
        using boost::asio::ip::udp;

        _socket = SocketPointer
            (new udp::socket(_io_service, udp::endpoint(udp::v4(), 0)));

        setAddress(IP, port);
    }

    /**
     * @brief Creates an instance of an UDP socket suitable for client
     * communication and sets server address.
     *
     * @param address Provided Address object.
     */
    UDPClient(const Address& address) : _io_service()
    {
        using boost::asio::ip::udp;

        _socket = SocketPointer
            (new udp::socket(_io_service, udp::endpoint(udp::v4(), 0)));

        setAddress(address);
    }

    /**
     * @brief Destructor.
     */
    ~UDPClient() { }

    /**
     * @brief Gets server address object.
     * @return Server Address.
     */
    const Address& getAddress() const
    {
        return _server_address;
    }

    /**
     * @brief Sets server address using provided IP and port.
     * @param IP   Provided IP address.
     * @param port Provided port.
     */
    void setAddress(const std::string& IP, boost::uint16_t port)
    {
        _server_address = Address(IP, port);
    }

    /**
     * @brief Sets server address object.
     * @param address Provided Address object.
     */
    void setAddress(const Address& address)
    {
        _server_address = address;
    }

    /**
     * @brief Resolves server location.
     */
    void resolve()
    {
        using boost::asio::ip::udp;

        // "Stringified" port is required.
        std::stringstream ss;
        ss << _server_address.getPort();

        // Retrieves server endpoint from IP and port.

        udp::resolver resolver(_io_service);

        udp::resolver::query query(udp::v4(),
                                   _server_address.getIP(), ss.str());

        udp::resolver::iterator iterator(resolver.resolve(query));

        _endpoint = iterator->endpoint();
    }

    /**
     * @brief Sets server address, then resolves server location.
     * @param IP   Provided IP address.
     * @param port Provided port.
     */
    void resolve(const std::string& IP, boost::uint16_t port)
    {
        setAddress(IP, port);
        resolve();
    }

    /**
     * @brief Sets server address, then resolves server location.
     * @param address Provided Address object.
     */
    void resolve(const Address& address)
    {
        setAddress(address);
        resolve();
    }

    /**
     * @brief Sends requests over UDP to the server.
     * @param src_buf A char pointer containing the source buffer.
     * @param buf_len Provided buffer length.
     * @return Number of bytes transmitted.
     */
    size_t send(char* src_buf, size_t buf_len)
    {
        return _socket->send_to(boost::asio::buffer(src_buf, buf_len),
                                _endpoint);
    }

    /**
     * @brief Sends a size_t type over UDP to the server.
     * @param size The provided value to send.
     * @return Number of bytes transmitted.
     */
    size_t send(const size_t& size)
    {
        return _socket->send_to(boost::asio::buffer(&size, sizeof(size_t)),
                                _endpoint);
    }

    /**
     * @brief Sends a string over UDP to the server.
     * @param str The provided string to send.
     * @return Number of bytes transmitted.
     */
    size_t send(const std::string& str)
    {
        return _socket->send_to(boost::asio::buffer(str), _endpoint);
    }

    /**
     * @brief Receives responses over UDP from the server.
     * @param dest_buf The provided char pointer to fill.
     * @param buf_len provided buffer length.
     * @return Number of bytes transmitted.
     */
    size_t receive(char* dest_buf, size_t buf_len)
    {
        return _socket->receive_from(boost::asio::buffer(dest_buf, buf_len),
                                     _endpoint);
    }

    /**
     * @brief Receives a size_t type over UDP from the server.
     * @param size A reference to the size_t to write.
     * @return Number of bytes transmitted.
     */
    size_t receive(size_t& size)
    {
        return _socket->receive_from(boost::asio::buffer(&size, sizeof(size_t)),
                                     _endpoint);
    }
};
